using System;
using System.Collections.Generic;
using System.Text;

namespace SharpMud.Text.Dynamic.Processing
{
    /// <summary>
    /// This internalizes all the work involved in actually taking the parsed data and putting it in a state
    /// where it is ready to be compiled.
    /// </summary>
    internal class ProcessedDynamicTextBody : IProcessedDynamicTextBody
    {
        #region Private Variables
        private System.CodeDom.CodeTypeDeclaration          _CodeTypeDeclaration;
        private List<System.CodeDom.CodeNamespaceImport>    _CodeNamespaceImports;
        private string                                      _Namespace;
        private System.CodeDom.Compiler.CodeDomProvider     _CodeDomProvider;
        private List<System.CodeDom.CodeStatement>          _CodeStatements;
        private HashWrapper                                 _SourceHash;
        #endregion

        #region Constructors
        private ProcessedDynamicTextBody()
        {
            this._CodeNamespaceImports = new List<System.CodeDom.CodeNamespaceImport>();
            this._CodeTypeDeclaration = new System.CodeDom.CodeTypeDeclaration();
            this._CodeStatements = new List<System.CodeDom.CodeStatement>();
        }
        internal ProcessedDynamicTextBody(INugget[] nuggets, HashWrapper sourceHash, System.CodeDom.Compiler.CodeDomProvider codeDomProvider)
            : this()
        {
            this._SourceHash = sourceHash;
            this._CodeDomProvider = codeDomProvider;
            AddNuggets(nuggets);
            this._CodeTypeDeclaration.IsClass = true;
            this._CodeTypeDeclaration.BaseTypes.Add(typeof(ICompiledDynamicTextBody));
            this._CodeTypeDeclaration.CustomAttributes.Add(GenerateSourceHashGuidAttribute());
            this._CodeTypeDeclaration.Members.Add(CreateMemberMethod(this._CodeStatements.ToArray()));
            this._CodeTypeDeclaration.Attributes = System.CodeDom.MemberAttributes.Public;
        }       
        #endregion

        private System.CodeDom.CodeAttributeDeclaration GenerateSourceHashGuidAttribute()
        {
            System.CodeDom.CodeAttributeDeclaration cad = new System.CodeDom.CodeAttributeDeclaration(
                new System.CodeDom.CodeTypeReference(typeof(Metadata.SourceHashGuidAttribute)) );
            cad.Arguments.Add(
                new System.CodeDom.CodeAttributeArgument(
                    new System.CodeDom.CodePrimitiveExpression(this._SourceHash.ToGUID().ToString())
                )   );
            return cad;
        }

        private static System.CodeDom.CodeExpression _WriteObject;
        public static System.CodeDom.CodeExpression WriteObject
        {
            get
            {
                if (_WriteObject == null)
                {
                    _WriteObject = new System.CodeDom.CodeFieldReferenceExpression(
                    new System.CodeDom.CodeVariableReferenceExpression("context"),
                    "Output");
                }
                return _WriteObject;
            }
        }

        private System.CodeDom.CodeMemberMethod CreateMemberMethod(System.CodeDom.CodeStatement[] codeStatements)
        {
            System.CodeDom.CodeMemberMethod cmm = new System.CodeDom.CodeMemberMethod();
            cmm.Name = "Execute";
            cmm.Attributes = System.CodeDom.MemberAttributes.Public;
            cmm.Parameters.Add(new System.CodeDom.CodeParameterDeclarationExpression(typeof(IDynamicTextExecutionContext), "context"));
            cmm.ImplementationTypes.Add(typeof(ICompiledDynamicTextBody));
            cmm.Statements.AddRange(codeStatements);
            return cmm;
        }

        #region AddNugget support
        private void AddNuggets(INugget[] nuggets)
        {
            //First get the metacommands, because they contain important compile information
            foreach (INugget n in nuggets)
            {
                if (n is MetacommandNugget)
                {
                    AddNugget(n as MetacommandNugget);
                }
            }
            //Now loop through and process all the other nuggets
            foreach (INugget n in nuggets)
            {
                if (!(n is MetacommandNugget))
                {
                    AddNugget(n);
                }
            }
        }

        private void AddNugget(INugget nugget)
        {
            if (nugget is MetacommandNugget)
                AddNugget((MetacommandNugget)nugget);
            
            else if (nugget is RawTextNugget)
                AddNugget((RawTextNugget)nugget);
            
            else if (nugget is RawCodeNugget)
                AddNugget((RawCodeNugget)nugget);
            
            else if (nugget is TextExpressionNugget)
                AddNugget((TextExpressionNugget)nugget);
            
            else
                throw new ArgumentException("Argument 'nugget' is not of a recognized type.");
        }

        private void AddNugget(MetacommandNugget nugget)
        {
            if (nugget.Name.Trim().ToLower() == "page")
            {
                Metacommands.Page page = Metacommands.Page.FromMetacommand(nugget);
                if (page.Language != String.Empty)
                {
                    string l = page.Language;
                    if (l.IndexOf(",") > 0)
                    {
                        System.Type t = Type.GetType(l, false, true);
                        if (t != null)
                        {
                            this._CodeDomProvider = (System.CodeDom.Compiler.CodeDomProvider)t.Assembly.CreateInstance(t.FullName,true);
                        }                    
                    }
                }
                if (page.ClassName != String.Empty)
                {
                    this._CodeTypeDeclaration.Name = page.ClassName;
                }
                if (page.Namespace != String.Empty)
                {
                    this._Namespace = page.Namespace;
                }
            }
            else if (nugget.Name.Trim().ToLower() == "import")
            {
                Metacommands.Import import = Metacommands.Import.FromMetacommand(nugget);
                this._CodeNamespaceImports.Add(new System.CodeDom.CodeNamespaceImport(import.Namespace));
            }
        }

        private void AddNugget(RawTextNugget nugget)
        {
            _CodeStatements.Add( nugget.ToCodeStatement(WriteObject, "Write"));
        }

        private void AddNugget(RawCodeNugget nugget)
        {
            LanguageBoundCode<System.CodeDom.CodeSnippetStatement> lbc_css = nugget.ToCodeStatement();
            if (lbc_css.CodeDomProvider != null && this._CodeDomProvider != null && !(lbc_css.CodeDomProvider.IsInstanceOfType(this._CodeDomProvider)))
            {
                throw new InvalidOperationException("Incompatible CodeDomProvider's.");
            }
            _CodeStatements.Add(lbc_css.CodeObject);
        }

        private void AddNugget(TextExpressionNugget nugget)
        {
            LanguageBoundCode<System.CodeDom.CodeStatement> lbc_cs = nugget.ToCodeStatement(WriteObject, "Write");
            if (lbc_cs.CodeDomProvider != null && this._CodeDomProvider != null && !(lbc_cs.CodeDomProvider.IsInstanceOfType(this._CodeDomProvider)))
            {
                throw new InvalidOperationException("Incompatible CodeDomProvider's.");
            }
            
            _CodeStatements.Add(lbc_cs.CodeObject);
        }

        #endregion

        #region Public Properties
        //public System.CodeDom.CodeTypeDeclaration CodeTypeDeclaration
        //{
        //    get { return this._CodeTypeDeclaration; }
        //    set { this._CodeTypeDeclaration = value; }
        //}

        //public IList<System.CodeDom.CodeNamespaceImport> CodeNamespaceImports
        //{
        //    get { return this._CodeNamespaceImports; }
        //}

        //string IProcessedDynamicTextBody.Namespace
        //{
        //    get { return this._Namespace; }
        //    set { this._Namespace = value; }
        //}

        //System.CodeDom.Compiler.CodeDomProvider IProcessedDynamicTextBody.CodeDomProvider
        //{
        //    get { return this._CodeDomProvider; }
        //    set { this._CodeDomProvider = value; }
        //}

        //HashWrapper IProcessedDynamicTextBody.SourceHash
        //{
        //    get { return this._SourceHash; }
        //    set { this._SourceHash = value; }
        //}
        #endregion

        #region IProcessedDynamicTextBody Members

        System.CodeDom.CodeTypeDeclaration IProcessedDynamicTextBody.CodeTypeDeclaration
        {
            get { return this._CodeTypeDeclaration; }
        }

        System.CodeDom.CodeNamespaceImport[] IProcessedDynamicTextBody.CodeNamespaceImports
        {
            get { return this._CodeNamespaceImports.ToArray(); }
        }

        string IProcessedDynamicTextBody.Namespace
        {
            get { return this._Namespace; }
        }

        System.CodeDom.Compiler.CodeDomProvider IProcessedDynamicTextBody.CodeDomProvider
        {
            get { return this._CodeDomProvider; }
        }

        HashWrapper IProcessedDynamicTextBody.SourceHash
        {
            get { return this._SourceHash; }
        }

        LanguageBoundCode<System.CodeDom.CodeCompileUnit> IProcessedDynamicTextBody.ToCodeCompileUnit()
        {
            System.CodeDom.CodeCompileUnit ccu = new System.CodeDom.CodeCompileUnit();
            
            System.CodeDom.CodeNamespace cn = new System.CodeDom.CodeNamespace(this._Namespace);
            cn.Imports.AddRange(this._CodeNamespaceImports.ToArray());
            cn.Types.Add(this._CodeTypeDeclaration);

            ccu.Namespaces.Add(cn);

            LanguageBoundCode<System.CodeDom.CodeCompileUnit> lbc_ccu = new LanguageBoundCode<System.CodeDom.CodeCompileUnit>(ccu, this._CodeDomProvider);

            return lbc_ccu;
        }
        #endregion
    }
}
